package GoPing

import (
	"context"
	"net"
	"sync"
	"time"

	"gitee.com/liumou_site/logger"
)

// 使用緩存來儲存已驗證過的 IP 地址或主機名，避免重複查詢
var (
	validIPCache     = make(map[string]bool)
	validIPCacheLock sync.RWMutex
)

// IsIP 檢查給定的字符串是否為有效的IP地址或域名。
// 它首先檢查緩存中是否已經驗證過這個地址，然後嘗試將字符串解析為IPv4或IPv6地址。
// 如果解析失敗，則嘗試將字符串解析為主機名，並通過DNS查詢驗證該主機名是否存在。
// 參數:
//
//	ip - 待檢查的字符串，可能是IP地址或主機名。
//
// 返回值:
//
//	如果字符串是有效的IP地址或可解析的主機名，則返回true；否則返回false。
func IsIP(ip string) bool {
	// 首先檢查緩存
	validIPCacheLock.RLock()
	result, exists := validIPCache[ip]
	validIPCacheLock.RUnlock()

	if exists {
		return result
	}

	// 緩存中沒有，嘗試解析
	valid := isValidIP(ip)

	// 將結果添加到緩存
	validIPCacheLock.Lock()
	validIPCache[ip] = valid
	validIPCacheLock.Unlock()

	return valid
}

// isValidIP 實際執行 IP 地址或主機名驗證
func isValidIP(ip string) bool {
	// 嘗試將字符串解析為 IP 地址
	if net.ParseIP(ip) != nil {
		return true
	}

	// 如果不是有效的 IP 地址，嘗試作為主機名解析
	// 設置超時，避免 DNS 解析過久
	resolver := &net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			dialer := net.Dialer{
				Timeout: 3 * time.Second, // 設置較短的超時時間
			}
			return dialer.DialContext(ctx, network, address)
		},
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	_, err := resolver.LookupHost(ctx, ip)
	return err == nil
}

// FilterIP 過濾給定的IP地址列表，返回有效的IP地址數組。
// 該函數併發地驗證每個輸入的IP地址字符串，檢查其是否為有效的IP地址或可解析的主機名。
// 參數:
//
//	ips []string: 待檢查的IP地址列表。
//
// 返回值:
//
//	[]string: 有效的IP地址列表。
func FilterIP(ips []string) []string {
	if len(ips) == 0 {
		return []string{}
	}

	var validIPs []string
	var wg sync.WaitGroup
	var mu sync.Mutex

	// 併發檢查每個 IP 地址
	for _, ip := range ips {
		wg.Add(1)
		go func(addr string) {
			defer wg.Done()

			if IsIP(addr) {
				mu.Lock()
				validIPs = append(validIPs, addr)
				mu.Unlock()
			} else {
				logger.Error("無效的 IP 地址或域名:", addr)
			}
		}(ip)
	}

	wg.Wait()
	return validIPs
}
